/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
/**
 * Software License, Version 1.0
 *
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 *
 *
 *Redistribution and use in source and binary forms, with or without
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For
 * further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 *
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering,
 * shall not be used to endorse or promote products derived from this software
 * without prior written permission from Indiana University.  For written
 * permission, please contact the Advanced Research and Technology Institute
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering,
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 *
 *
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of
 * any other entity.  Indiana University disclaims any liability to any
 * recipient for claims brought by any other entity based on infringement of
 * intellectual property rights or otherwise.
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.transport.ptcp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;

import cgl.narada.transport.Link;
import cgl.narada.transport.LinkFactory;
import cgl.narada.transport.TransportDebugFlags;
import cgl.narada.transport.TransportException;
import cgl.narada.transport.TransportHandler;
import cgl.narada.transport.ptcp.psocket.PTCPSocket;

/** This is a TCP implementation of the LinkFactory Interface. Lets assume that
    the connection is being initiated by a node A to another node B. There are
    two independent factors that need to borne in mind while using LinkFactory.
    The first is of course setting the link factory properties. The second is
    specifying the properties to create a link to some other node B.

    <p>
    To initialize the TCPLinkFactory one needs to specify the <b>TCPServerPort
    </b>. When the value of this variable is set to <b>0</b> it implies that
    the node A initiating a connection to node B will not accept link creation
    requests from any other node.
    <p>
    To create a link to node B. The following properties needs to be specified.
    <li><b>hostname</b>: This is the hostname on which the node B's process is
    running.
    <li><b>portnum</b>: This is the port number on which node B accepts link
    creation requests from other nodes. In other words, the <i>TCPServerPort
    </i> specified to the TCPChannelFactory at node B is equal to portnum.

    @author Shrideep Pallickara
    $Date$
    $Revision$
 */

public class PTCPLinkFactory
    implements LinkFactory, TransportDebugFlags {

  private String managedLinkType = "ptcp";
  private Properties properties;
  private String moduleName = "PTCPLinkFactory: ";
  private int ptcpServerSocketPort = 0;
  private PTCPServerThread ptcpServerThread;

  private Hashtable links;
  private TransportHandler transportHandler;

  public PTCPLinkFactory() {
    links = new Hashtable();
  }

  /* These are the set of properties that are needed to set up the
     link Factory. */
  public void setLinkFactoryProperties(Properties factoryProperties) {
    if (factoryProperties == null) {
      System.out.println(moduleName + "The specified properties is NULL");
      return;
    }
    properties = factoryProperties;
    String _valueString = properties.getProperty("PTCPServerPort");
    if (_valueString != null) {
      ptcpServerSocketPort = Integer.parseInt(_valueString);
    }
    else {
      System.out.println(moduleName + "Property PTCPServerPort missing!");
    }
  }

  /** Return the type of all the links created/managed by this factory */
  public String getManagedLinksType() {
    return managedLinkType;
  }

  /** Create a communication link based on the properties that have been
      specified. */
  public Link createLink(Properties linkProperties) throws TransportException {
    if (linkProperties == null) {
      throw new TransportException(moduleName +
                                   "The specified link propertis is null");
    }

    String _hostName = linkProperties.getProperty("hostname");
    String _portString = linkProperties.getProperty("portnum");
    String _numberOfStreamsString = linkProperties.getProperty(
        "PTCPStreamNumber");
    if(_numberOfStreamsString == null) _numberOfStreamsString ="2";

    if (_hostName == null || _portString == null ) {
      throw new TransportException(moduleName +
                                   "One of the specified properties is null");
    }
    int _portNum = Integer.parseInt(_portString);
    int _numberOfStreams = Integer.parseInt(_numberOfStreamsString);

    try {
      InetAddress _ia = InetAddress.getByName(_hostName);
      _hostName = _ia.getHostName();
      String _key = _hostName + ":" + _portNum;
      if (links.containsKey(_key)) {
        throw new TransportException(moduleName + "Link already exists to" +
                                     "ptcp://" + _key);
      }
      PTCPSocket psocket = new PTCPSocket(_ia, _portNum, _numberOfStreams);
      PTCPLink _link = new PTCPLink(psocket, this);
      _link.setLinkId(_key);

      links.put(_key, _link);
      transportHandler.registerLink(this, _link);
      _link.startLinkServices();
      return _link;
    }
    catch (UnknownHostException uhe) {
      String errorInfo = moduleName + "Unknown host [" + _hostName + "]" + uhe;
      System.out.println(errorInfo);
      throw new TransportException(errorInfo);
    }
    catch (IOException ioe) {
      String errorInfo = moduleName + " Unable to Setup Connection to [" +
          _hostName + ":" + _portNum + "] => " + ioe;
      System.out.println(errorInfo);
      throw new TransportException(errorInfo);
    }
  }

  /** Set the interval for checking status of all links managed by this
      factory to the one specified in the argument */
  public void setLinkStatusInterval(long interval) {

  }

  /** Start communication services. In the case of TCP for example, the
      thread which incorporates the serverSocket.accept() is started*/
  public boolean loadLinkFactoryServices() throws TransportException {
    if (ptcpServerSocketPort == 0) {
      /** This implies that there is no need to start up a TCPServerSocket
          This is true in the case of clients, who don't anticipate connections
          from other clients e.g. JMS Clients*/
      System.out.println(moduleName + "Will not be starting the PServerSocket" +
                         " Thread since the port specified is [0]");
      return true;
    }

    ptcpServerThread = new PTCPServerThread(ptcpServerSocketPort, this);
    ptcpServerThread.start();
    System.out.println("Loaded PTCPLinkFactory communication services");
    return true;
  }

  /** Indicates if this factory can manage migration of links of a certain
      type */
  public boolean canManageLinkMigrationOfType(String linkType) {
    /**NBNI*/
    return false;
  }

  /** Used to migrate communications of a link to the one supported by
      links managed by this factory */
  public void manageLinkMigration(Link link) throws TransportException {
    /**NBNI*/
  }

  /** Sets the reference to the transport handler. This is done during the
      loadLinkFactory() operation within the transport handler
      implementations. */
  public void setTransportHandler(TransportHandler transportHandler) {
    this.transportHandler = transportHandler;
  }

  /** This method is used to garbage collect any resources associated with
      the link factory */
  public void dispose() {
    Enumeration _links = links.elements();
    while (_links.hasMoreElements()) {
      PTCPLink _tcpLink = (PTCPLink) _links.nextElement();
      _tcpLink.closeLink();
    }

    if (ptcpServerThread != null) {
      if (PTCPLinkFactory_Debug) {
        System.out.println(moduleName + "Disposing PTCPServerThread");
      }
      ptcpServerThread.closeServerSocket();
      ptcpServerThread = null;
    }
  }

  public void handleInboundTCPLinkRequest(PTCPSocket psocket) {
    if (PTCPLinkFactory_Debug) {
      System.out.println(moduleName + "InboundConnection request received " +
                         psocket);
    }
    String _hostName = psocket.getInetAddress().getHostName();
    int _portNum = psocket.getPort();
    String _linkId = _hostName + ":" + _portNum;
    if (links.containsKey(_linkId)) {
      System.out.println(moduleName + "InboundConnection => " +
                         "Link aleady exists to " + _linkId);
    }
    try {
      PTCPLink _ptcpLink = new PTCPLink(psocket, this);
      _ptcpLink.setLinkId(_linkId);
      links.put(_linkId, _ptcpLink);
      transportHandler.registerLink(this, _ptcpLink);
      _ptcpLink.startLinkServices();
    }
    catch (TransportException transEx) {
      System.out.println("moduleName" + transEx);
    }
    /*end try-catch */
  }

  public void reportLinkLoss(Link linkLost) {
    String _linkId = linkLost.getLinkId();
    if (_linkId == null) {
      System.out.println(moduleName + "ReportLinkLoss Link Id is null");
      return;
    }
    if (links.containsKey(_linkId)) {
      transportHandler.manageLinkLoss( (Link) links.remove(_linkId));
      System.out.println(moduleName + "Managed Link Loss");
      return;
    }

    System.out.println(moduleName + "Unaware of link [" + _linkId +
                       "] whose connection loss is being reported");
  }

  public TransportHandler getTransportHandler() {
    if (transportHandler == null) {
      System.out.println(moduleName + "TransportHandler reference is NULL");
    }
    return transportHandler;
  }

  public static void main(String[] args) {
    PTCPLinkFactory factory = new PTCPLinkFactory();
    Properties props = new Properties();
    props.put("PTCPServerPort", args[0]);
    factory.setLinkFactoryProperties(props);
    BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
    String cmd;
    StringTokenizer tok;
    Hashtable links = new Hashtable();
    try {
      factory.loadLinkFactoryServices();
      System.out.println("\nType h for usage options");
      while (true) {
        cmd = r.readLine();

        if (cmd == null) {
          factory.dispose();
          break;
        }

        if (cmd.startsWith("h")) {
          System.out.println("cr <host> <port> \t \t \t Creates a link");
          System.out.println("cl <link-id> \t \t \t Closes a link");
          continue;
        }

        if (cmd.startsWith("cr")) {
          tok = new StringTokenizer(cmd, " ");
          if (tok.countTokens() != 3) {
            System.out.println("cr <host> <port> \t \t \t Creates a link");
            continue;
          }
          Properties _props = new Properties();
          tok.nextToken();
          _props.put("hostname", tok.nextToken());
          _props.put("portnum", tok.nextToken());
          Link _link = factory.createLink(_props);
          links.put(_link.getLinkId(), _link);
          System.out.println("Created link with id " +
                             _link.getLinkId());
        }
        /** if (creation of link */

        if (cmd.startsWith("cl")) {
          tok = new StringTokenizer(cmd, " ");
          if (tok.countTokens() != 2) {
            System.out.println("cl <link-id> \t \t \t Closes a link");
            continue;
          }
          tok.nextToken();
          String _linkId = tok.nextToken();
          if (links.containsKey(_linkId)) {
            Link _link = (Link) links.remove(_linkId);
            _link.closeLink();
          }
          else {
            System.out.println("Specified link Id is not correct!");
          }
        }
        /* if (closing of a specified link ) */

        if (cmd.equalsIgnoreCase("q")) {
          factory.dispose();
          break;
        }
      }
      /*end while(true)*/
    }
    catch (IOException ioe) {
      ioe.printStackTrace();
    }
    catch (TransportException transEx) {
      System.out.println(transEx);
    }
    /*end try-catch */
    System.out.println("PTCPLinkFactory: Exiting program ... end main()");
  }
  /*end-main*/

}
